﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using System.Collections.Specialized;
using Windows.Data.Xml.Dom;

namespace TakeItIn.Data
{
    public abstract class NoteDataCommon : TakeItIn.Common.BindableBase
    {
        private static Uri _baseUri = new Uri("ms-appx:///");

        public NoteDataCommon(String id, String title, String subtitle, String imagePath, String description)
        {
            this._id = id;
            this._title = title;
            this._subtitle = subtitle;
            this._description = description;
            //this._imagePath = imagePath;
        }

        private string _id = string.Empty;
        public string Id
        {
            get { return this._id; }
            set { this.SetProperty(ref this._id, value); }
        }

        private string _title = string.Empty;
        public string Title
        {
            get { return this._title; }
            set { this.SetProperty(ref this._title, value); }
        }

        private string _subtitle = string.Empty;
        public string Subtitle
        {
            get { return this._subtitle; }
            set { this.SetProperty(ref this._subtitle, value); }
        }

        private string _description = string.Empty;
        public string Description
        {
            get { return this._description; }
            set { this.SetProperty(ref this._description, value); }
        }

        private ImageSource _image = null;
        private String _imagePath = null;
        public ImageSource Image
        {
            get
            {
                if (this._image == null && this._imagePath != null)
                {
                    this._image = new BitmapImage(new Uri(NoteDataCommon._baseUri, this._imagePath));
                }
                return this._image;
            }

            set
            {
                this._imagePath = null;
                this.SetProperty(ref this._image, value);
            }
        }

        public void SetImage(String path)
        {
            this._image = null;
            this._imagePath = path;
            this.OnPropertyChanged("Image");
        }

        public override string ToString()
        {
            return this.Title;
        }
    }

    /// <summary>
    /// Generic item data model.
    /// </summary>
    public class Note : NoteDataCommon
    {
        public const string untitledNote = "Untitled";
        public const int maxReviewLevelCount = 3;

        public Note(String id, String title, String subtitle, String imagePath, String description, String content,
					NoteCategory category, DateTime lastEditTime)
            : base(id, title, subtitle, imagePath, description)
        {
            this._content = content;
            this._category = category;
            this._lastEditTime = lastEditTime;
            this._lastReviewTime = DateTime.MinValue;
            this._reviewFequencyLevel = 0;
            this._reviewLevelCount = 0;
        }

        public enum ReviewFrequency { Level0, Level1, Level2, Level3, Level4, Level5, Level6 };

        private string _content = string.Empty;
        public string Content
        {
            get { return this._content; }
            set { this.SetProperty(ref this._content, value); }
        }

        private NoteCategory _category;
        public NoteCategory Category
        {
            get { return this._category; }
            set { this.SetProperty(ref this._category, value); }
        }

        private DateTime _lastEditTime;
        public DateTime lastEditTime
        {
            get { return this._lastEditTime; }
            set { this.SetProperty(ref this._lastEditTime, value); }
        }

        private DateTime _lastReviewTime;
        public DateTime lastReviewTime
        {
            get { return this._lastReviewTime; }
            set { this.SetProperty(ref this._lastReviewTime, value); }
        }

        private int _reviewFequencyLevel;
        public int reviewFequencyLevel
        {
            get { return this._reviewFequencyLevel; }
            set { this.SetProperty(ref this._reviewFequencyLevel, value); }
        }

        private int _reviewLevelCount;
        public int reviewLevelCount
        {
            get { return this._reviewLevelCount; }
            set { this.SetProperty(ref this._reviewLevelCount, value); }
        }
    }

    /// <summary>
    /// Generic category data model.
    /// </summary>
    public class NoteCategory : NoteDataCommon
    {
        public const string allCategories = "All Categories";
        public const string untitledCategory = "Untitled Category";

        public NoteCategory(String id, String title, String subtitle, String imagePath, String description)
            : base(id, title, subtitle, imagePath, description)
        {
            Notes.CollectionChanged += NotesCollectionChanged;
        }

        private void NotesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // Provides a subset of the full items collection to bind to from a GroupedItemsPage
            // for two reasons: GridView will not virtualize large items collections, and it
            // improves the user experience when browsing through groups with large numbers of
            // items.
            //
            // A maximum of 12 items are displayed because it results in filled grid columns
            // whether there are 1, 2, 3, 4, or 6 rows displayed

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewStartingIndex < 12)
                    {
                        TopNotes.Insert(e.NewStartingIndex,Notes[e.NewStartingIndex]);
                        if (TopNotes.Count > 12)
                        {
                            TopNotes.RemoveAt(12);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    if (e.OldStartingIndex < 12 && e.NewStartingIndex < 12)
                    {
                        TopNotes.Move(e.OldStartingIndex, e.NewStartingIndex);
                    }
                    else if (e.OldStartingIndex < 12)
                    {
                        TopNotes.RemoveAt(e.OldStartingIndex);
                        TopNotes.Add(Notes[11]);
                    }
                    else if (e.NewStartingIndex < 12)
                    {
                        TopNotes.Insert(e.NewStartingIndex, Notes[e.NewStartingIndex]);
                        TopNotes.RemoveAt(12);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldStartingIndex < 12)
                    {
                        TopNotes.RemoveAt(e.OldStartingIndex);
                        if (Notes.Count >= 12)
                        {
                            TopNotes.Add(Notes[11]);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (e.OldStartingIndex < 12)
                    {
                        TopNotes[e.OldStartingIndex] = Notes[e.OldStartingIndex];
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    TopNotes.Clear();
                    while (TopNotes.Count < Notes.Count && TopNotes.Count < 12)
                    {
                        TopNotes.Add(Notes[TopNotes.Count]);
                    }
                    break;
            }
        }

        private ObservableCollection<Note> _notes = new ObservableCollection<Note>();
        public ObservableCollection<Note> Notes
        {
            get { return this._notes; }
        }

        private ObservableCollection<Note> _topItem = new ObservableCollection<Note>();
        public ObservableCollection<Note> TopNotes
        {
            get {return this._topItem; }
        }
    }

    public class CurrentNote
    {
        public string id { get; set; }
        public string categoryTitle { get; set; }
        public bool fReview { get; set; }
    }

    /// <summary>
    /// Creates a collection of groups and items with hard-coded content.
    /// 
    /// NoteDataSource initializes with placeholder data rather than live production
    /// data so that sample data is provided at both design-time and run-time.
    /// </summary>
    public sealed class NoteDataSource
    {
        private static NoteDataSource _noteDataSource = new NoteDataSource();

        private ObservableCollection<NoteCategory> _allGroups = new ObservableCollection<NoteCategory>();
        public ObservableCollection<NoteCategory> AllGroups
        {
            get { return this._allGroups; }
        }

        private ObservableCollection<NoteCategory> _reviewGroups = new ObservableCollection<NoteCategory>();
        public ObservableCollection<NoteCategory> ReviewGroups
        {
            get { return this._reviewGroups; }
        }

        public static ObservableCollection<NoteCategory> GetCategorys()
        {
            return _noteDataSource.AllGroups;
        }

        public static ObservableCollection<NoteCategory> GetReviewCategorys()
        {
            return _noteDataSource.ReviewGroups;
        }

        public static NoteCategory GetCategory(string title)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _noteDataSource.AllGroups.Where((category) => category.Title.Equals(title));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static NoteCategory GetReviewCategory(string title)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _noteDataSource.ReviewGroups.Where((category) => category.Title.Equals(title));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static Note GetNote(string id)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _noteDataSource.AllGroups.SelectMany(category => category.Notes).Where((item) => item.Id.Equals(id));
            if (matches.Count() > 0) return matches.First();
            return null;
        }

        public static NoteCategory CreateCategory(string title, bool fPrepend = false)
        {
            var category = GetCategory(title);

            if (category == null)
            {
                string idCategory = Guid.NewGuid().ToString();
                category = new NoteCategory(idCategory, title, null, null, null);
                if (fPrepend)
                    _noteDataSource.AllGroups.Insert(0, category);
                else
                    _noteDataSource.AllGroups.Add(category);
            }

            return category;
        }

        public static NoteCategory CreateReviewCategory(string title, bool fPrepend = false)
        {
            var category = GetReviewCategory(title);

            if (category == null)
            {
                string idCategory = Guid.NewGuid().ToString();
                category = new NoteCategory(idCategory, title, null, null, null);
                if (fPrepend)
                    _noteDataSource.ReviewGroups.Insert(0, category);
                else
                    _noteDataSource.ReviewGroups.Add(category);
            }

            return category;
        }

        public static Note CreateNote(string id, string categoryTitle, string title, string content, DateTime lastEditTime)
        {
            if (categoryTitle == "")
                categoryTitle = NoteCategory.untitledCategory;

            if (title == "")
                title = Note.untitledNote;

            //var reviewCategory = GetReviewCategory(categoryTitle);
            //if (reviewCategory == null)
            //{
            //    string idCategory = Guid.NewGuid().ToString();
            //    reviewCategory = new NoteCategory(idCategory, categoryTitle, null, null, null);
            //    _noteDataSource.ReviewGroups.Insert(0, reviewCategory);
            //}

            var category = GetCategory(categoryTitle);
            if (category == null)
            {
                category = CreateCategory(categoryTitle, true);
            }

            Note note;
            string idNote = id;
            if (id == null)
            {
                idNote = Guid.NewGuid().ToString();
                note = new Note(idNote, title, null, null, null, content, category, lastEditTime);
                category.Notes.Insert(0, note);
                //reviewCategory.Notes.Insert(0, note);
            }
            else
            {
                note = new Note(idNote, title, null, null, null, content, category, lastEditTime);
                category.Notes.Add(note);
                //reviewCategory.Notes.Add(note);
            }
            return note;
        }

        public static bool ChangeNote(string id, string categoryTitle, string title, string content, DateTime lastEditTime)
        {
            Note note = GetNote(id);
            if (note == null)
                return false;

            if (title == "")
                title = Note.untitledNote;

            if (categoryTitle == "")
                categoryTitle = NoteCategory.untitledCategory;

            NoteCategory category = GetCategory(categoryTitle);
            if (note.Category != category)
            {
                if (note.Category.Notes.Count == 1)
                    _noteDataSource.AllGroups.Remove(note.Category);
                else
                    note.Category.Notes.Remove(note);

                NoteCategory reviewCategory = GetReviewCategory(note.Category.Title);
                bool fNeedReview = false;
                if (reviewCategory.Notes.Contains(note))
                {
                    fNeedReview = true;
                    if (reviewCategory.Notes.Count == 1)
                        _noteDataSource.ReviewGroups.Remove(reviewCategory);
                    else
                        reviewCategory.Notes.Remove(note);
                }

                if (category == null)
                {
                    category = NoteDataSource.CreateCategory(categoryTitle, true);
                    NoteDataSource.CreateReviewCategory(categoryTitle, true);
                }
                category.Notes.Insert(0, note);
                note.Category = category;
                if (fNeedReview)
                    GetReviewCategory(note.Category.Title).Notes.Insert(0, note);
            }

            note.Title = title;
            note.Content = content;
            note.lastEditTime = lastEditTime;
            note.lastReviewTime = DateTime.MinValue;

            return true;
        }

        public static async Task LoadNotes(Windows.Storage.StorageFile file = null)
        {
            if (file == null)
            {
                try
                {
                    file = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync("Notes.xml");
                }
                catch (Exception e) { }
            }
            if (file != null)
            {
                XmlDocument xmldoc = await XmlDocument.LoadFromFileAsync(file);

                XmlElement root = xmldoc.ChildNodes[0] as XmlElement;
                for (int i = 0; i < root.ChildNodes.Count; i++)
                {
                    XmlElement elmntCat = root.ChildNodes[i] as XmlElement;
                    string titleCategory = elmntCat.GetAttribute("title");
                    NoteDataSource.CreateCategory(titleCategory);
                    var reviewCategory = NoteDataSource.CreateReviewCategory(titleCategory);
                    for (int j = 0; j < elmntCat.ChildNodes.Count; j++)
                    {
                        XmlElement elmntNote = elmntCat.ChildNodes[j] as XmlElement;
                        Note note = NoteDataSource.CreateNote(elmntNote.GetAttribute("id"),
                                                    titleCategory,
                                                    elmntNote.GetAttribute("title"),
                                                    elmntNote.GetAttribute("content"),
                                                    DateTime.Parse(elmntNote.GetAttribute("lastEditTime")));
                        note.lastReviewTime = DateTime.Parse(elmntNote.GetAttribute("lastReviewTime"));

                        if (ShouldReviewNote(ref note))
                            reviewCategory.Notes.Add(note);
                    }
                }
            }
        }

        public static async Task SaveNotes()
        {
            XmlDocument xmldoc = new XmlDocument();
            XmlElement root = xmldoc.CreateElement("TakeItIn");
            xmldoc.AppendChild(root);
            IEnumerable<NoteCategory> allNoteCategories = NoteDataSource.GetCategorys();

            foreach (NoteCategory cat in allNoteCategories)
            {
                XmlElement elmntCat = xmldoc.CreateElement("Category");
                elmntCat.SetAttribute("title", cat.Title);
                root.AppendChild(elmntCat);

                foreach (Note note in cat.Notes)
                {
                    XmlElement elmntNote = xmldoc.CreateElement("Note");
                    elmntNote.SetAttribute("id", note.Id);
                    elmntNote.SetAttribute("title", note.Title);
                    var doc = new Windows.Data.Xml.Dom.XmlDocument();
                    var cdata = doc.CreateCDataSection(note.Content);
                    elmntNote.SetAttribute("content", cdata.Data.ToString());
                    elmntNote.SetAttribute("lastEditTime", note.lastEditTime.ToString());
                    elmntNote.SetAttribute("lastReviewTime", note.lastReviewTime.ToString());
                    elmntNote.SetAttribute("reviewFequencyLevel", note.reviewFequencyLevel.ToString());
                    elmntNote.SetAttribute("reviewLevelCount", note.reviewLevelCount.ToString());
                    elmntCat.AppendChild(elmntNote);
                }
            }

            try
            {
                var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync("Notes.xml", Windows.Storage.CreationCollisionOption.OpenIfExists);
                await xmldoc.SaveToFileAsync(file);
            }
            catch (Exception ex)
            {
                string st = ex.Message;
            }
        }

        public static bool ShouldReviewNote(ref Note note)
        {
            DateTime shouldReviewDate = GetReviewDate(note);
            if (shouldReviewDate.Date <= DateTime.Today.Date)
            {
                TimeSpan delta = DateTime.Today.Date - shouldReviewDate;
                if (delta.Days <= 1)
                {
                    return true;
                }
                else
                {
                    note.reviewLevelCount = 0;
                    if (note.reviewFequencyLevel > 0)
                        note.reviewFequencyLevel--;
                    return true;
                }
            }
            return false;
        }

        public static DateTime GetReviewDate(Note note)
        {
            int[] interval = new int[6];
            interval[0] = 1;
            interval[1] = 2;
            interval[2] = 3;
            interval[3] = 7;
            interval[4] = 14;
            interval[5] = 30;

            if (note.reviewLevelCount == Note.maxReviewLevelCount)
            {
                if (note.reviewFequencyLevel < interval.Length - 1)
                    return note.lastReviewTime.Date + new TimeSpan(0, interval[note.reviewFequencyLevel + 1], 0);
            }
            else if (note.reviewLevelCount < Note.maxReviewLevelCount)
            {
                return note.lastReviewTime.Date + new TimeSpan(0, interval[note.reviewFequencyLevel], 0);
            }

            return DateTime.MaxValue.Date;
        }

        public NoteDataSource()
        {
        }
    }
}
